using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Drawing.Imaging;
using MacomberMap.Common.Components;
using MacomberMap.Common.Internals;
using MacomberMap.Common.Database_Interface;
using MacomberMap.Common.User_Interfaces.Components;
using MacomberMap.Common.User_Interfaces.Network_Map.Components;

namespace MacomberMap.Common.User_Interfaces.Network_Map
{
    /// <summary>
    /// This class provides a GDI+ network map
    /// </summary>
    public partial class MM_NetworkMap : UserControl
    {
        #region Variable declarations
        /// <summary>Our collection of tiles</summary>
        private Dictionary<Point, MM_MapTile> Tiles = new Dictionary<Point, MM_MapTile>();

        /// <summary>The point at which the mouse was pressed</summary>
        private Point MouseDownPoint;        

        /// <summary>Our Image Attributes for drawing the map</summary>
        private ImageAttributes DrawAttributes = new ImageAttributes();      

        /// <summary>Our list of substations currently visible on the screen</summary>
        public List<MM_NetworkMap_Substation> VisibleSubs = new List<MM_NetworkMap_Substation>();

        /// <summary>Our list of lines currently visible on the screen</summary>
        public List<MM_NetworkMap_Line> VisibleLines = new List<MM_NetworkMap_Line>();

        /// <summary>Our list of Boundarys currently visible on the screen</summary>
        public List<MM_NetworkMap_Boundary> VisibleBoundaries = new List<MM_NetworkMap_Boundary>();

        /// <summary>The menu for right-click events</summary>
        private ContextMenuStrip RightClickMenu;

        /// <summary>Our substation that may be moving</summary>
        private MM_NetworkMap_Substation MovingSubstation = null;

        /// <summary>Our substation that may be moving's latitude and longitude</summary>
        private PointF MovingSubstationOriginalLatLng;

        /// <summary>Our database connection</summary>
        private MM_Database DbConnection = null;

        /// <summary>Our internal representation of substations</summary>
        private Dictionary<MM_Substation, MM_NetworkMap_Substation> Substations;

        /// <summary>Our internal representation of counties</summary>
        private Dictionary<MM_Boundary, MM_NetworkMap_Boundary> Counties;

        /// <summary>Our internal representation of the state boundary</summary>
        private MM_NetworkMap_Boundary StateBoundary;

        /// <summary>Our internal representation of lines</summary>
        private Dictionary<MM_Line, MM_NetworkMap_Line> Lines;

        /// <summary>
        /// The display options driving the current UI
        /// </summary>
        public MM_NetworkMap_Display DisplayOptions = new MM_NetworkMap_Display(-99.25309f, 30.76527f, 9);

        /// <summary>
        /// The default display options for network maps
        /// </summary>
        public static MM_NetworkMap_Display DefaultDisplayOptions;
        #endregion

        #region Event declarations

        /// <summary>
        /// A delegate indicating an event on an element
        /// </summary>
        /// <param name="Element">The element upon which the event occurred</param>
        public delegate void ElementEventDelegate(MM_Element Element);

        /// <summary>An element was highlighted</summary>
        public event MouseEventHandler ElementMouseDown;

        /// <summary>An element has been moved</summary>
        public event MouseEventHandler ElementMouseMove;
        
        /// <summary>An element's property was updated</summary>
        public event ElementEventDelegate PropertyUpdated;

        
        #endregion
        

        #region Initialization
        /// <summary>
        /// Initialize our network map, and update our coordinates
        /// </summary>
        public MM_NetworkMap()
        {
            InitializeComponent();            
            ColorMatrix OutMatrix = new ColorMatrix(new float[][] { new float[] { 1, 0, 0, 0, 0 }, new float[] { 0, 1, 0, 0, 0 }, new float[] { 0, 0, 1, 0, 0 }, new float[] { 0, 0, 0, 0.5f, 0 }, new float[] { 0, 0, 0, 0, 1 } });
            this.DrawAttributes.SetColorMatrix(OutMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
            this.DoubleBuffered = true;
            try
            {
                InitializeElements();
                RecomputeElements();
            }
            catch (Exception) { }
            RightClickMenu = new ContextMenuStrip();
            //TODO: Add back in right-click menu handling
            //RightClickMenu.ItemClicked += new ToolStripItemClickedEventHandler(RightClickMenu_ItemClicked);
            
        }

        /// <summary>
        /// Set up our network map representations of substations, lines, and boundaries
        /// </summary>
        private void InitializeElements()
        {
            
            Substations = new Dictionary<MM_Substation, MM_NetworkMap_Substation>(MM_Repository.Substations.Count + 50);
            foreach (MM_Substation Sub in MM_Repository.Substations.Values)
                Substations.Add(Sub, new MM_NetworkMap_Substation(Sub, DisplayOptions));

            Lines = new Dictionary<MM_Line,MM_NetworkMap_Line>(MM_Repository.Lines.Count + 50);
            foreach (MM_Line Line in MM_Repository.Lines.Values)
                Lines.Add(Line, new MM_NetworkMap_Line(Line, DisplayOptions));

            Counties = new Dictionary<MM_Boundary,MM_NetworkMap_Boundary>(MM_Repository.Counties.Count);
            foreach (MM_Boundary Bound in MM_Repository.Counties.Values)
                Counties.Add(Bound, new MM_NetworkMap_Boundary(Bound, DisplayOptions));

            StateBoundary = new MM_NetworkMap_Boundary(MM_Repository.StateBoundary, DisplayOptions);
        }

        /// <summary>
        /// Load an XML file into our repository
        /// </summary>
        /// <param name="FileName">The file name to be loaded</param>
        public void LoadXMLIntoRepository(string FileName)
        {
            MM_Coordinate_Interface.LoadCoordinates(FileName);
            RecomputeElements();
        }

        /// <summary>
        /// Assign a database connection to the map
        /// </summary>
        /// <param name="DbConnection">The database connection to assign</param>
        public void AssignDatabase(MM_Database DbConnection)
        {
            this.DbConnection = DbConnection;
        }
        #endregion

        #region Map drawing
        /// <summary>
        /// Update our map transparency
        /// </summary>
        /// <param name="TransparencyLevel">The intended transparency level</param>
        public void SetTransparency(string TransparencyLevel)
        {
            try
            {
                float MapTransparency = MM_Repository.ToSingle(TransparencyLevel.Substring(0, TransparencyLevel.Length - 1)) / 100f;
                ColorMatrix OutMatrix = new ColorMatrix(new float[][] { new float[] { 1, 0, 0, 0, 0 }, new float[] { 0, 1, 0, 0, 0 }, new float[] { 0, 0, 1, 0, 0 }, new float[] { 0, 0, 0, MapTransparency, 0 }, new float[] { 0, 0, 0, 0, 1 } });
                this.DrawAttributes.SetColorMatrix(OutMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
                this.Invalidate();
            }
            catch (Exception)
            { }
        }
        /// <summary>
        /// Draw all tiles within our map
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaint(PaintEventArgs e)
        {
            //Go through all tiles, and draw accordingly
            Point TopLeftTile = MM_NetworkMap_Display.XYToTile(DisplayOptions.TopLeftXY);
            Point BottomRightTile = MM_NetworkMap_Display.XYToTile(DisplayOptions.BottomRightXY);
            Point TopLeftTileShift = new Point(DisplayOptions.TopLeftXY.X % MM_NetworkMap_Display.TileSize.Width, DisplayOptions.TopLeftXY.Y % MM_NetworkMap_Display.TileSize.Height);
            
            //First, draw our tiles if requested            
            if (DisplayOptions.MapTiles != MM_MapTile.MapType.None)
                for (Point ThisTile = new Point(TopLeftTile.X, TopLeftTile.Y); ThisTile.X <= BottomRightTile.X; ThisTile.X++, ThisTile.Y = TopLeftTile.Y)
                    for (; ThisTile.Y <= BottomRightTile.Y; ThisTile.Y++)
                    {
                        if (!Tiles.ContainsKey(ThisTile))
                        {
                            MM_MapTile NewTile = new MM_MapTile(DisplayOptions.MapTiles, ThisTile, DisplayOptions.ZoomLevel, DbConnection);
                            NewTile.TileUpdated += new MM_MapTile.TileUpdatedDelegate(NewTile_TileUpdated);
                            Tiles.Add(ThisTile, NewTile);
                        }
                        MM_MapTile TileToDraw = Tiles[ThisTile];
                        if (!TileToDraw.Saving)
                            e.Graphics.DrawImage(TileToDraw.Tile, new Rectangle(((ThisTile.X - TopLeftTile.X) * MM_NetworkMap_Display.TileSize.Width) - TopLeftTileShift.X, ((ThisTile.Y - TopLeftTile.Y) * MM_NetworkMap_Display.TileSize.Height) - TopLeftTileShift.Y, MM_NetworkMap_Display.TileSize.Width, MM_NetworkMap_Display.TileSize.Height), 0, 0, MM_NetworkMap_Display.TileSize.Width, MM_NetworkMap_Display.TileSize.Height, GraphicsUnit.Pixel, DrawAttributes);
                    }

            //Shift our coordinates
            e.Graphics.TranslateTransform(-DisplayOptions.TopLeftXY.X, -DisplayOptions.TopLeftXY.Y);




            //Next, draw our counties if requested
            VisibleBoundaries.Clear();
            VisibleSubs.Clear();
            VisibleLines.Clear();
            if (DisplayOptions.ShowCounties)
            {
                foreach (KeyValuePair<MM_Boundary, MM_NetworkMap_Boundary> Bound in Counties)
                    if (Bound.Value.Visible = DisplayOptions.IsVisible(Bound.Key))
                    {
                        e.Graphics.DrawPath(new Pen(Color.FromArgb(32, 32, 32), 1), Bound.Value.CountyPath);
                        VisibleBoundaries.Add(Bound.Value);
                    }
                if (StateBoundary != null)
                    e.Graphics.DrawPath(Pens.White, StateBoundary.CountyPath);
            }


            //Next, draw our lines if requested)            
            foreach (KeyValuePair<MM_Line, MM_NetworkMap_Line> Line in Lines)
                if (float.IsNaN(Line.Value.BaseLine.Substation1.LatLong.X) || float.IsNaN(Line.Value.BaseLine.Substation2.LatLong.X))
                    Line.Value.Visible = false;
                else if (Line.Value.Visible = DisplayOptions.IsVisible(Line.Key))
                    {
                        VisibleLines.Add(Line.Value);
                        e.Graphics.DrawLine(Line.Key.KVLevel.Energized.ForePen, MM_NetworkMap_Display.LatLngToXY(Line.Key.Substation1.LatLong, DisplayOptions.ZoomLevel), MM_NetworkMap_Display.LatLngToXY(Line.Key.Substation2.LatLong, DisplayOptions.ZoomLevel));
                    }

            //Next, draw our substations if requested            
                foreach (KeyValuePair<MM_Substation, MM_NetworkMap_Substation> Sub in Substations)
                    if (Sub.Value.Visible = DisplayOptions.IsVisible(Sub.Key))
                    {
                        Sub.Value.DrawSubstation(e.Graphics, DisplayOptions);                        
                        VisibleSubs.Add(Sub.Value);
                    }


        }





        /// <summary>
        /// Update a tile
        /// </summary>
        /// <param name="UpdatedTile"></param>
        private void NewTile_TileUpdated(MM_MapTile UpdatedTile)
        {
            this.Invalidate();
        }

        /// <summary>
        /// Recompue the paths for all of our counties and state
        /// </summary>
        private void RecomputeElements()
        {
            foreach (MM_NetworkMap_Boundary County in Counties.Values)
                County.ComputeCounty(DisplayOptions);
            if (StateBoundary != null)
                StateBoundary.ComputeCounty(DisplayOptions);
            foreach (MM_NetworkMap_Substation Sub in Substations.Values)
                Sub.RecomputeSubstation(DisplayOptions);
            foreach (MM_NetworkMap_Line Line in Lines.Values)
                Line.RecomputeLine(DisplayOptions);
        }
        #endregion

        #region Mouse event handling
        /// <summary>
        /// Handle mouse scrolling
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseWheel(MouseEventArgs e)
        {
            DisplayOptions.UpdateZoom(DisplayOptions.ZoomLevel + Math.Sign(e.Delta), this);
            RecomputeElements();

            Tiles.Clear();
            this.Invalidate();
        }


        /// <summary>
        /// Handle mouse pressing down
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                MouseDownPoint = Cursor.Position;                
                Point CurrentPos = new Point(e.X + DisplayOptions.TopLeftXY.X, e.Y + DisplayOptions.TopLeftXY.Y);
                PointF MouseDownLatLng = MM_NetworkMap_Display.XYToLatLng(CurrentPos, DisplayOptions.ZoomLevel);
                foreach (MM_NetworkMap_Substation Sub in VisibleSubs)
                    if (Sub.HitTest(CurrentPos))
                    {                        
                        MovingSubstation = Sub;
                        MovingSubstationOriginalLatLng = Sub.BaseStation.LatLong;
                        if (ElementMouseDown != null)
                            ElementMouseDown(Sub.BaseStation, e);                        
                        return;
                    }

                foreach (MM_NetworkMap_Line Line in VisibleLines)
                    if (Line.HitTest(CurrentPos))
                    {
                        if (ElementMouseDown != null)
                            ElementMouseDown(Line.BaseLine, e);                        
                        return;
                    }


                /*
                foreach (MM_NetworkMap_Boundary Bound in VisibleBoundaries)
                    if (Bound.BaseBoundary.HitTest(MouseDownLatLng))
                    {
                        if (ElementMouseDown != null)
                            ElementMouseDown(Bound.BaseBoundary, e);
                        return;
                    }    */   
                if (PropertyUpdated != null)
                    PropertyUpdated(null);                

            }
            base.OnMouseDown(e);
        }

        //Handle mouse movement
        /// <summary>
        /// Handle mouse movement within the network map
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && MouseDownPoint.X > -1)
            {
                if (MovingSubstation != null && ElementMouseMove != null)
                    ElementMouseMove(MovingSubstation, e);                                                    
                else if (MovingSubstation == null)               
                    DisplayOptions.UpdateFromXY(new Point(DisplayOptions.TopLeftXY.X - Cursor.Position.X + MouseDownPoint.X, DisplayOptions.TopLeftXY.Y - Cursor.Position.Y + MouseDownPoint.Y), this);                    
                MouseDownPoint = Cursor.Position;
                this.Invalidate();
            }

            //TODO: Handle mouse movement again
            //(this.FindForm() as frmNetworkMapEditor).UpdateInformation(e.Location, Coordinates);
            base.OnMouseMove(e);
        }

        /// <summary>
        /// Handle the mouse button up
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseUp(MouseEventArgs e)
        {
            MouseDownPoint = new Point(-1, -1);
            if (MovingSubstation != null)
            {
                foreach (KeyValuePair<MM_Line, MM_NetworkMap_Line> Line in Lines)                    
                    if (Line.Key.Substation1 == MovingSubstation.BaseStation || Line.Key.Substation2 == MovingSubstation.BaseStation)
                        Line.Value.RecomputeLine(DisplayOptions);
                if (PropertyUpdated != null)
                    PropertyUpdated(MovingSubstation.BaseStation);
                MovingSubstation = null;
            }
            if (e.Button == MouseButtons.Right)
                HandlePopup(new Point(e.X + DisplayOptions.TopLeftXY.X, e.Y + DisplayOptions.TopLeftXY.Y));

            base.OnMouseUp(e);
        }

        /// <summary>
        /// Handle a mouse right-click up event
        /// </summary>
        /// <param name="point"></param>
        private void HandlePopup(Point point)
        {


            RightClickMenu.Items.Clear();
            RightClickMenu.Tag = point;
            foreach (MM_NetworkMap_Substation Sub in VisibleSubs)
                if (Sub.HitTest(point))
                {
                    Sub.BaseStation.BuildMenuItems(RightClickMenu);                   
                    RightClickMenu.Show(Cursor.Position);
                    return;
                }
            foreach (MM_NetworkMap_Line Line in VisibleLines)
                if (Line.HitTest(point))
                {
                    Line.BaseLine.BuildMenuItems(RightClickMenu);
                    RightClickMenu.Show(Cursor.Position);
                    return;
                }
            RightClickMenu.Items.Add("Add &substation");
            RightClickMenu.Items.Add("Add &line");
            RightClickMenu.Show(Cursor.Position);
        }

       


        /// <summary>
        /// Handle user keypresses
        /// </summary>
        /// <param name="e"></param>
        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            
            if (e.KeyChar == (char)Keys.Escape && MovingSubstation != null)
            {
                //TODO: Handle queue handling when we hit escape
                //(ParentForm as frmNetworkMapEditor).QueueUndoEvent(MovingSubstation.BaseStation, "LatLong", MovingSubstation.BaseStation.LatLong, MovingSubstationOriginalLatLng);                
                MovingSubstation = null;
                this.Invalidate();
            }
            else if (Char.ToLower(e.KeyChar) == 'r')
            {
                DisplayOptions.UpdateFromLatLng(new PointF(-99.25309f, 30.76527f), this);
                this.Invalidate();
            }
            else if (e.KeyChar == '-')
            {
                DisplayOptions.UpdateZoom(DisplayOptions.ZoomLevel - 1, this);
                RecomputeElements();

                Tiles.Clear();
                this.Invalidate();
            }
            else if (e.KeyChar == '+')
            {
                DisplayOptions.UpdateZoom(DisplayOptions.ZoomLevel + 1, this);
                RecomputeElements();

                Tiles.Clear();
                this.Invalidate();
            }
            base.OnKeyPress(e);
        }

        /// <summary>
        /// Update and redraw the map when our size changes
        /// </summary>
        /// <param name="e"></param>
        protected override void OnSizeChanged(EventArgs e)
        {
            DisplayOptions.UpdateFromLatLng(DisplayOptions.TopLeft, this);
            base.OnSizeChanged(e);
        }
        /// <summary>
        /// Update the map source to the specified string
        /// </summary>
        /// <param name="MapSource"></param>
        public void SetMapSource(String MapSource)
        {
            try
            {
                DisplayOptions.MapTiles = (MM_MapTile.MapType)Enum.Parse(typeof(MM_MapTile.MapType), MapSource);
                Tiles.Clear();
                this.Invalidate();
            }
            catch (Exception)
            { }
        }

        /// <summary>
        /// Shift the map over at the requested zoom level so that the center is the requested lat/long.
        /// </summary>
        /// <param name="CenterLatLng"></param>
        public void SetCenter(PointF CenterLatLng)
        {
            DisplayOptions.SetCenter(CenterLatLng, this);
            this.Invalidate();
        }
        #endregion

    }
}